/*
 * luaR.c
 */

#include <utils/error.h>
#include "luaR.h"
#include "luaRcallback.h"

lua_State *state;
int lua_initialized = 0;

/*
 * Initializes the LuaR interface. Will attempt to open the file
 * "scripts/preferences.lua" and execute the code inside, establishing the
 * globals to be used by this Lua state.
 */
int luaR_initialize() {
    if( lua_initialized ) {
        printerr( WARNING_LUA_REINIT );
        return 1;
    }
    else {
        state = lua_open();
        if( state == NULL ) {
            fprintf( stdout, "lua_open() call failed!\n" );
            return -1;
        }
        lua_pushcfunction(state, luaopen_base);
        lua_call(state, 0, 0);
        lua_pushcfunction(state, luaopen_table);
        lua_call(state, 0, 0);
        lua_pushcfunction(state, luaopen_string);
        lua_call(state, 0, 0);
        lua_pushcfunction(state, luaopen_math);
        lua_call(state, 0, 0);
        lua_pushcfunction(state, luaopen_io);
        lua_call(state, 0, 0);
#ifdef LUA_VERSION_NUM
        lua_pushcfunction(state, luaopen_os);
        lua_call(state, 0, 0);
#endif
        lua_initialized = 1;
        if( luaR_callback_initialize() ) {
            /* TODO: error */
        }
        if( luaR_load_file("scripts/preferences.lua") ) {
            /* TODO: Use error interface. */
            fprintf( stderr, "ERROR: Failed to load preferences!\n" );
        }
        return 0;
    }
}

/*
 * Creates a new LuaR package. The name of the package is not set until
 * luaR_package_set() is called.
 * Internally, this function creates a new table and pushes it onto the Lua
 * stack.
 */
int luaR_package_create() {
    if( lua_initialized ) {
        lua_newtable(state);
        return 0;
    }
    else {
        printerr( ERROR_LUA_PKGNOINIT );
        return 1;
    }
}

/*
 * Adds the given C function to the current Lua package with the given name.
 * Once the package is set, this function can be accessed at any time in a Lua
 * script by calling "packageName.function()".
 * A table must be at the top of the stack, preferably created through
 * luaR_package_create().
 */
int luaR_package_add( const char *name, lua_CFunction f ) {
    if( lua_initialized ) {
        lua_pushstring(state, name);
        lua_pushcfunction(state, f);
        lua_rawset(state, lua_gettop(state) - 2); /* t[name] = f */
        return 0;
    }
    else {
        printerr( ERROR_LUA_ADDPKGINIT );
        return 1;
    }
}

/*
 * Sets the current package on the stack with the current name. After this is
 * called, all functions bound to this package can be accessed at any time in a
 * Lua script by calling "packageName.function()"
 * A table must be at the top of the stack, preferably created through
 * luaR_package_create()
 */
int luaR_package_set( const char *name ) {
    if( lua_initialized ) {
        lua_setglobal(state, name);
        return 0;
    }
    else {
        printerr( ERROR_LUA_SETPKGINIT );
        return 1;
    }
}

/*
 * Registers pkg_name.fn_name as a Lua callback with id fn_id.
 * This function should only be called by ROWGAGE functions!
 */
int luaR_add_callback( const char *pkg_name, const char *fn_name, int fn_id ) {
    lua_getfield(state, LUA_GLOBALSINDEX, pkg_name); /* 1 */
    lua_getfield(state, LUA_GLOBALSINDEX, "RG_builtin"); /* 2 */
    lua_getfield(state, lua_gettop(state), "cCallback"); /* 3 */
    lua_pushinteger(state, fn_id); /* 4 */
    lua_call(state, 1, 1); /* 3 */
    lua_setfield(state, lua_gettop(state) - 2, fn_name); /* 2 */
    lua_pop(state, 2);
    return 0;
}

/*
 * Attempts to load the given filename as a Lua script and if successful will
 * run the script.
 */
int luaR_load_file( const char *file ) {
    int status;
    status = luaL_loadfile(state, file);
    if( status ) {
        printerr( ERROR_LUA_OPENFAIL, lua_tostring(state, lua_gettop(state)) );
        return status;
    }
    status = lua_pcall(state, 0, LUA_MULTRET, 0);
    if( status ) {
        printerr( ERROR_LUA_INITFAIL, lua_tostring(state, lua_gettop(state)) );
        return status;
    }
    return 0;
}

/*
 * Closes the Lua state.
 */
int luaR_close() {
    if( lua_initialized ) {
        lua_close(state);
        lua_initialized = 0;
        return 0;
    }
    else {
        printerr( WARNING_LUA_CLOSE );
        return 1;
    }
}

/*
 * Attempts to call the given Lua function.
 */
int luaR_fcall( const char *f ) {
    int status;
    if( lua_initialized ) {
        lua_pushstring(state, f);
        lua_gettable(state, LUA_GLOBALSINDEX);
        status = lua_pcall(state, 0, 0, 0);
        if( status ) {
            printerr( ERROR_LUA_SCRIPTFAIL,
                lua_tostring(state, lua_gettop(state)) );
            return status;
        }
        return 0;
    }
    else {
        printerr( ERROR_LUA_NOINIT );
        return 1;
    }
}

/*
 * Returns the value of t[k] for the current Lua state as a string, first
 * evaluating the value of k to determine the key to use.
 * TODO: better name
 */
const char * luaR_getconstants2( const char *t, const char *k ) {
    const char *result;
    if( lua_initialized ) {
        lua_getglobal(state, t);
        lua_getglobal(state, k);
        lua_gettable(state, lua_gettop(state) - 1);
        result = lua_tostring(state, lua_gettop(state));
        lua_pop(state, 2);
        return result;
    }
    else {
        printerr( ERROR_LUA_NOINIT );
        return NULL;
    }
}


/*
 * Returns the value of t[k] for the current Lua state as an int.
 */
int luaR_getconstanti( const char *t, const char *k ) {
    int result;
    if( lua_initialized ) {
        lua_getglobal(state, t);
        lua_pushstring(state, k);
        lua_gettable(state, lua_gettop(state) - 1);
        result = (int)lua_tonumber(state, lua_gettop(state));
        lua_pop(state, 2);
        return result;
    }
    else {
        printerr( ERROR_LUA_NOINIT );
        return 0;
    }
}

/*
 * Returns the value of t[k] for the current Lua state as a string.
 */
const char * luaR_getconstants( const char *t, const char *k ) {
    const char *result;
    if( lua_initialized ) {
        lua_getglobal(state, t);
        lua_pushstring(state, k);
        lua_gettable(state, lua_gettop(state) - 1);
        result = lua_tostring(state, lua_gettop(state));
        lua_pop(state, 2);
        return result;
    }
    else {
        printerr( ERROR_LUA_NOINIT );
        return NULL;
    }
}
